<?php

namespace CommonBundle\View;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Exception\ValidatorException;

trait UpdateApiViewMixin
{
    protected static $MODE_CREATE = 0;
    protected static $MODE_UPDATE = 1;

    //protected $requiredUpdateProperties = [];
    //protected $acceptedUpdateProperties = [];

    /**
     * @return array
     */
    protected function defaultValues(): array
    {
        /** Default values */
        return [];
    }

    /**
     * @param array $content
     * @param null $entity
     * @return array
     */
    protected function processContent(array $content, $entity = null): array
    {
        /** Default values */
        return $content;
    }

    /**
     * @param $entity
     * @return mixed
     */
    protected function after($entity)
    {
        /** Updated entity */
        return $entity;
    }


    /**
     * @return array
     */
    protected function defaultUpdateValues(): array
    {
        return $this->defaultValues();
    }

    /**
     * @param array $content
     * @param null $entity
     * @return array
     */
    protected function processUpdateContent(array $content, $entity = null): array
    {
        return $this->processContent($content, $entity);
    }

    /**
     * @param $entity
     * @return mixed
     */
    protected function afterUpdated($entity)
    {
        return $this->after($entity);
    }

    /**
     * @param $entity
     * @param $content
     * @param array|null $transformer
     * @param int $writeMode
     * @return mixed
     */
    private function updateSingle($entity, $content, array $transformer = null, int $writeMode = 1 /* MODE_UPDATE */)
    {
        $service = $this->get($this->serviceClass);

        // properties process.
        if(
            property_exists($this, 'requiredUpdateProperties') ||
            property_exists($this, 'acceptedUpdateProperties')
        ) {
            $data = [];

            if (property_exists($this, 'requiredUpdateProperties')) {
                foreach ($this->requiredUpdateProperties as $property) {
                    if (!array_key_exists($property, $content)) {
                        throw new ValidatorException(ucfirst($property) . " cannot be empty.");
                    }
                    $data[$property] = $content[$property];
                }
            }

            if (property_exists($this, 'acceptedUpdateProperties')) {
                foreach ($this->acceptedUpdateProperties as $property) {
                    if (array_key_exists($property, $content)) {
                        $data[$property] = $content[$property];
                    }
                }
            }


            $content = $data;
        }

        // process content
        $content = array_merge($content,
            $writeMode
                ? $this->defaultUpdateValues()
                : (
                    method_exists($this, 'defaultCreateValues')
                    ? $this->{'defaultCreateValues'}()
                    : $this->defaultValues()
                )
        );

        if($transformer) {
            $content = $this->transformContent($content, $transformer, $entity);
        }
        $content = $writeMode
            ? $this->processUpdateContent($content, $entity)
            : (
                method_exists($this, 'processCreateContent')
                ? $this->{'processCreateContent'}($content, $entity)
                : $this->processContent($content, $entity)
            )
        ;

        // remove id
        unset($content['id']);

        // save
        $entity = $service->update($entity, $content);
        return $writeMode
            ? $this->afterUpdated($entity)
            : (
                method_exists($this, 'afterCreated')
                ? $this->{'afterCreated'}($entity)
                : $this->after($entity)
            );
    }

    /**
     * @param Request $request
     * @param null $id
     * @return array|mixed
     * @throws \Exception
     */
    private function updateRecords(Request $request, $id = null)
    {
        $service = $this->get($this->serviceClass);

        // External content
        $content = json_decode($request->getContent(), true) ? : [];

        // Batch mode
        // update, mixed
        $mode = $request->query->get('@mode', 'mixed');

        // Update basis
        // eg: id, name, ...
        $basis = $request->query->get('@basis');
        $basis = $basis ? array_map(function($item) { return trim($item); }, explode(',', $basis)) : [];

        // Partial create / update
        $partial = $request->query->getBoolean('@partial', false);

        // Transformer
        $transformer = $request->query->get('@transform');
        if($transformer) {
            $transformer = json_decode($transformer, true);
        }

        // Start

        if($id) {
            // Single update
            $filter = $this->mixIdToCommonFilter($id);
            $entity = $service->get($filter, false);
            return $this->updateSingle($entity, $content, $transformer);
        }
        elseif(is_array($content)) {
            // Multiple update
            $em = $this->get('doctrine.orm.entity_manager');
            $response = [];

            $em->beginTransaction();
            try {
                foreach ($content as $item) {
                    // Get entity search data
                    $data = [];
                    foreach ($basis as $basisItem) {
                        $data[$basisItem] = $item[$basisItem];
                    }

                    $filter = $this->mixToCommonFilter($data);
                    $entity = $service->get($filter, false);
                    $writeMode = self::$MODE_UPDATE;

                    if(empty($entity) || empty($basis)) {
                        if($mode == 'mixed') {
                            $writeMode = self::$MODE_CREATE;
                            $entity = $service->new();
                        }
                        else continue;
                    }

                    $response[] = $this->updateSingle($entity, $item, $transformer, $writeMode);
                }
                $em->commit();
            }
            catch (\Exception $exception) {
                if(!$partial) {
                    $em->rollback();
                    throw $exception;
                }
                // else {
                //     If partial insert is available, do nothing
                // }
            }
            return $response;
        }
        else {
            throw new ValidatorException('Content type error.');
        }
    }

    /**
     * Api multiple update view
     *
     * @Route("/batch-update", name="batch-update", methods={"POST"})
     * @SWG\Response(
     *     response=200,
     *     description="Api multiple update view",
     * )
     * @SWG\Parameter(name="@mode", in="query", type="string")
     * @SWG\Parameter(name="@basis", in="query", type="string")
     * @SWG\Parameter(name="@partial", in="query", type="boolean")
     * @SWG\Parameter(name="@transform", in="query", type="string")
     * @SWG\Parameter(
     *     name="body",
     *     in="body",
     *     description="Json content",
     *     type="json",
     *     required=false,
     *     @SWG\Schema(type="object")
     * )
     *
     * @param Request $request
     * @return Response
     * @throws \Exception
     */
    public function batchUpdateAction(Request $request): Response
    {
        $response = $this->updateRecords($request);

        if($response === null) {
            throw new ValidatorException('Batch update error');
        }
        else {
            return $this->Success($response);
        }
    }

    /**
     * Api update view
     *
     * @Route("/{id}", name="update", methods={"PUT"}, requirements={"id"="\d+"})
     * @SWG\Parameter(name="@transform", in="query", type="string")
     * @SWG\Response(
     *     response=200,
     *     description="Api update view",
     * )
     * @SWG\Parameter(
     *     name="body",
     *     in="body",
     *     description="Json content",
     *     type="json",
     *     required=false,
     *     @SWG\Schema(type="object")
     * )
     *
     * @param Request $request
     * @param $id
     * @return Response
     * @throws \Exception
     */
    public function updateAction(Request $request, $id): Response
    {
        if ($response = $this->updateRecords($request, $id)) {
            return $this->Success($response);
        } else {
            return $this->Warning();
        }
    }
}

